package cn.xlbweb.cli.config;

import cn.xlbweb.cli.mapper.OperateLogMapper;
import cn.xlbweb.cli.model.OperateLog;
import cn.xlbweb.cli.model.User;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: bobi
 * @date: 2020-04-11 12:42
 * @description:
 */
@Configuration
@Aspect
@Slf4j
public class CliAspectConfig {

    @Autowired
    private OperateLogMapper operateLogMapper;

    @Pointcut("@annotation(cn.xlbweb.cli.config.CliOperateLog)")
    public void cliOperateLog() {
    }

    @Around("cliOperateLog()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("CliOperateLog start...");

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // 构造OperateLog对象
        OperateLog operateLog = new OperateLog();

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest httpServletRequest = attributes.getRequest();
        // 请求URI
        operateLog.setUri(httpServletRequest.getRequestURI());
        // 客户端IP地址
        operateLog.setIp(httpServletRequest.getRemoteAddr());

        Map<String, Object> parameterMap = new HashMap<>(64);
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        // 请求参数
        String[] parameterNames = methodSignature.getParameterNames();
        Object[] args = proceedingJoinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            parameterMap.put(parameterNames[i], args[i]);
        }
        operateLog.setParameter(JSON.toJSONString(parameterMap));

        // 操作模块、操作类型、操作描述
        CliOperateLog cliOperateLog = methodSignature.getMethod().getAnnotation(CliOperateLog.class);
        operateLog.setModule(cliOperateLog.module());
        operateLog.setType(cliOperateLog.type());
        operateLog.setDescription(cliOperateLog.description());

        // 方法名
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        String methodName = methodSignature.getMethod().getName();
        operateLog.setMethod(className + "." + methodName);

        // 操作人
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        if (user == null) {
            operateLog.setOperator("AnonymousUser");
        } else {
            operateLog.setOperator(user.getUsername());
        }

        // TODO 返回状态码

        // 注意:控制层和业务层的代码想要都统计到,需要写到耗时统计结束之前
        Object proceed = proceedingJoinPoint.proceed();

        // 请求耗时
        stopWatch.stop();
        operateLog.setTime(stopWatch.getTotalTimeMillis() + "毫秒");

        // 不能让插入日志失败影响正常业务逻辑
        try {
            int count = operateLogMapper.insertSelective(operateLog);
            if (count > 0) {
                log.info("操作日志插入成功，主键id={}", operateLog.getId());
            } else {
                log.error("插入操作日志失败");
            }
        } catch (Exception e) {
            log.error("插入操作日志异常", e);
        }

        log.info("CliOperateLog end...");
        return proceed;
    }
}
